import default_messages from './locale/zh-CN';
import { extend, clone, format, queueEach, isBlank } from './utils';

var pattern = {
    //参考：http://emailregex.com/
    email: /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
    //参考：https://www.itdaan.com/blog/2016/01/04/405f514a3babd1ed7dc29965811636a4.html
    url: /(https?|ftp|file):\/\/[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/,
    //参考：https://www.cnblogs.com/6489c/p/5985022.html
    color: /^(#?([a-f0-9]{6}|[a-f0-9]{3})){1}$/i,
    //参考：https://www.cnblogs.com/lvxisha/p/10827236.html
    mobile: /^[1][3,4,5,7,8,9][0-9]{9}$/
};

const i18nMessages = {
    'zh-CN': default_messages
};

const messages = clone(default_messages);

const default_options = {
    messages: messages,
    firstError: true
}

const validators = {

    required(value, rule, label, messages) {
        if (rule.required) {
            if (isBlank(value)) {
                return createError(rule, messages.required, [label]);
            }
        }
    },
    selected(value, rule, label, messages) {
        if (rule.selected) {
            if (isBlank(value)) {
                return createError(rule, messages.selected, [label]);
            }
        }
    },
    type(value, rule, label, messages) {
        if (rule.type) {
            if (isBlank(value)) {
                return;
            }
            if (typeof(value) != rule.type) {
                if (rule.type == "array") {
                    if (value instanceof Array) {
                        return;
                    }
                } else if (rule.type == "integer") {
                    if (typeof(value) == "number" && value % 1 === 0) {
                        return;
                    }
                } else if (rule.type == "date") {
                    if (value instanceof Date) {
                        return;
                    }
                } else if (rule.type == "float") {
                    if (value + ".0" != value) {
                        return;
                    }
                } else if (rule.type == "regexp") {
                    if (value instanceof RegExp) {
                        return;
                    }
                } else if (rule.type == "email") {
                    if (typeof(value) == 'string' && value.match(pattern.email)) {
                        return;
                    }
                } else if (rule.type == "url") {
                    if (typeof(value) == 'string' && value.match(pattern.url)) {
                        return;
                    }
                } else if (rule.type == "color") {
                    if (typeof(value) == 'string' && value.match(pattern.color)) {
                        return;
                    }
                } else if (rule.type == 'mobile') {
                    if (typeof(value) == 'string' && value.match(pattern.mobile)) {
                        return;
                    }
                }
                return createError(rule, messages.types[rule.type], [label, rule.type]);
            }
        }
    },
    len(value, rule, label, messages) {
        if (typeof(rule.len) == "number") {
            var type = typeof(value);
            if (type == 'string') {
                if (value.length != rule.len) {
                    return createError(rule, messages.len.string, [label, rule.len]);
                }
            } else if (type == 'number') {
                if (value != rule.len) {
                    return createError(rule, messages.len.number, [label, rule.len]);
                }
            } else if (value instanceof Array) {
                if (value.length != rule.len) {
                    return createError(rule, messages.len.array, [label, rule.len]);
                }
            }
        }
    },
    min(value, rule, label, messages) {
        if (typeof(rule.min) == "number") {
            var type = typeof(value);
            if (type == 'string') {
                if (value.length < rule.min) {
                    return createError(rule, messages.min.string, [label, rule.min]);
                }
            } else if (type == 'number') {
                if (value < rule.min) {
                    return createError(rule, messages.min.number, [label, rule.min]);
                }
            } else if (value instanceof Array) {
                if (value.length < rule.min) {
                    return createError(rule, messages.min.array, [label, rule.min]);
                }
            }
        }
    },
    max(value, rule, label, messages) {
        if (typeof(rule.max) == "number") {
            var type = typeof(value);
            if (type == 'string') {
                if (value.length > rule.max) {
                    return createError(rule, messages.max.string, [label, rule.max]);
                }
            } else if (type == 'number') {
                if (value > rule.max) {
                    return createError(rule, messages.max.number, [label, rule.max]);
                }
            } else if (value instanceof Array) {
                if (value.length > rule.max) {
                    return createError(rule, messages.max.array, [label, rule.max]);
                }
            }
        }
    },
    range(value, rule, label, messages) {
        if (rule.range instanceof Array && rule.range.length > 1) {
            var min = rule.range[0];
            var max = rule.range[1];
            var type = typeof(value);
            if (type == 'string') {
                if (value.length < min || value.length > max) {
                    return createError(rule, messages.range.string, [label, min, max]);
                }
            } else if (type == 'number') {
                if (value < min || value > max) {
                    return createError(rule, messages.range.number, [label, min, max]);
                }
            } else if (value instanceof Array) {
                if (value.length < min || value.length > max) {
                    return createError(rule, messages.range.array, [label, min, max]);
                }
            }
        }
    },
    validate(value, rule, label, messages) {
        if (rule.validate instanceof Function) {
            return rule.validate(value, rule, label, messages);
        }
    }

};

export const createError = (rule, message, args) => {
    return new Error(format(rule.message || message, args));
}

const createDefaultError = (name, label, rule) => {
    var error = createError(rule, messages.default, [label]);
    error.name = name;
    return error;
}

const validateRuleObject = (name, label, value, rule, callback, options) => {
    var allErrors = [];
    var messages = options.messages;
    var firstError = options.firstError;

    function finish() {
        if (firstError && allErrors.length > 0) {
            callback(false, allErrors);
            return;
        }
        if (this.hasNext()) {
            this.next();
        } else {
            callback(allErrors.length == 0, allErrors);
        }
    }
    queueEach(rule, function(item, validatorName) {
        var validator = validators[validatorName];
        if (validator && validator instanceof Function) {
            var error = validator(value, rule, label, messages);
            if (error instanceof Error) {
                error.name = name;
                allErrors.push(error);
                finish.call(this);
            } else if (error instanceof Promise) {
                error.then((err) => {
                    if (err instanceof Error) {
                        err.name = name;
                        allErrors.push(err);
                    } else if (err == false) {
                        allErrors.push(createDefaultError(name, label, rule));
                    }
                    finish.call(this);
                });
            } else if (error == false) {
                allErrors.push(createDefaultError(name, label, rule));
                finish.call(this);
            } else {
                finish.call(this);
            }
        } else {
            finish.call(this);
        }
    });
}

const validateRuleArray = (name, value, ruleArray, callback, options) => {
    var allErrors = [];
    var firstError = options.firstError;
    var label = name;
    var count = 0;
    queueEach(ruleArray, function(rule, index) {
        label = rule.label || label;
        validateRuleObject(name, label, value, rule, (valid, errors) => {
            if (!valid) {
                for (var j = 0; j < errors.length; j++) {
                    allErrors.push(errors[j]);
                }
            }
            if (firstError && allErrors.length > 0) {
                //第一个错误返回
                callback(false, allErrors);
                return;
            }
            if (this.hasNext()) {
                this.next();
            } else {
                callback(allErrors.length == 0, allErrors);
            }
        }, options);
    });
}

export const load = (name, message) => {
    i18nMessages[name] = message;
}

export const i18n = (_messages) => {
    if (typeof(_messages) == "string") {
        extend(messages, i18nMessages[_messages]);
    } else {
        extend(messages, _messages);
    }
}

export const registerValidator = (name, validator) => {
    validators[name] = validator;
}

export const removeValidator = (name) => {
    delete validators[name];
}

export const validate = (valueObject, rules, callback = function(valid, errors) {}, options = {}) => {
    var rule = null;
    var label = null;
    var opts = extend({}, default_options, options);
    var allErrors = [];

    function finish() {
        if (this.hasNext()) {
            this.next();
        } else {
            callback(allErrors.length == 0, allErrors);
        }
    }
    queueEach(rules, function(rule, name) {
        var next = (valid, errors) => {
            if (!valid) {
                for (var i = 0; i < errors.length; i++) {
                    allErrors.push(errors[i]);
                }
            }
            finish.call(this);
        }
        if (rule instanceof Array) {
            validateRuleArray(name, valueObject[name], rule, next, opts);
        } else {
            label = rule['label'] || name;
            validateRuleObject(name, label, valueObject[name], rule, next, opts);
        }
    });
}

export default {
    load,
    i18n,
    validate,
    registerValidator,
    removeValidator
};